<template>
    <div class="el-table"
         :class="[{
      'el-table--fit': fit,
      'el-table--striped': stripe,
      'el-table--border': border || isGroup,
      'el-table--hidden': isHidden,
      'el-table--group': isGroup,
      'el-table--fluid-height': maxHeight,
      'el-table--scrollable-x': layout.scrollX,
      'el-table--scrollable-y': layout.scrollY,
      'el-table--enable-row-hover': !store.states.isComplex,
      'el-table--enable-row-transition': (store.states.data || []).length !== 0 && (store.states.data || []).length < 100
    }, tableSize ? `el-table--${ tableSize }` : '']"
         @mouseleave="handleMouseLeave($event)">
        <div class="hidden-columns" ref="hiddenColumns">
            <slot></slot>
        </div>
        <div
                v-if="showHeader"
                v-mousewheel="handleHeaderFooterMousewheel"
                class="el-table__header-wrapper"
                ref="headerWrapper">
            <table-header
                    ref="tableHeader"
                    :store="store"
                    :border="border"
                    :default-sort="defaultSort"
                    :style="{
          width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
        }">
            </table-header>
        </div>
        <div
                class="el-table__body-wrapper"
                ref="bodyWrapper"
                :class="[layout.scrollX ? `is-scrolling-${scrollPosition}` : 'is-scrolling-none']"
                :style="[bodyHeight]">
            <table-body
                    :context="context"
                    :store="store"
                    :stripe="stripe"
                    :row-class-name="rowClassName"
                    :row-style="rowStyle"
                    :highlight="highlightCurrentRow"
                    :style="{
           width: bodyWidth
        }">
            </table-body>
            <div
                    v-if="!data || data.length === 0"
                    class="el-table__empty-block"
                    ref="emptyBlock"
                    :style="emptyBlockStyle">
        <span class="el-table__empty-text">
          <slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot>
        </span>
            </div>
            <div
                    v-if="$slots.append"
                    class="el-table__append-wrapper"
                    ref="appendWrapper">
                <slot name="append"></slot>
            </div>
        </div>
        <div
                v-if="showSummary"
                v-show="data && data.length > 0"
                v-mousewheel="handleHeaderFooterMousewheel"
                class="el-table__footer-wrapper"
                ref="footerWrapper">
            <table-footer
                    :store="store"
                    :border="border"
                    :sum-text="sumText || t('el.table.sumText')"
                    :summary-method="summaryMethod"
                    :default-sort="defaultSort"
                    :style="{
          width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
        }">
            </table-footer>
        </div>
        <div
                v-if="fixedColumns.length > 0"
                v-mousewheel="handleFixedMousewheel"
                class="el-table__fixed"
                ref="fixedWrapper"
                :style="[{
        width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
      },
      fixedHeight]">
            <div
                    v-if="showHeader"
                    class="el-table__fixed-header-wrapper"
                    ref="fixedHeaderWrapper">
                <table-header
                        ref="fixedTableHeader"
                        fixed="left"
                        :border="border"
                        :store="store"
                        :style="{
            width: bodyWidth
          }"></table-header>
            </div>
            <div
                    class="el-table__fixed-body-wrapper"
                    ref="fixedBodyWrapper"
                    :style="[{
          top: layout.headerHeight + 'px'
        },
        fixedBodyHeight]">
                <table-body
                        fixed="left"
                        :store="store"
                        :stripe="stripe"
                        :highlight="highlightCurrentRow"
                        :row-class-name="rowClassName"
                        :row-style="rowStyle"
                        :style="{
            width: bodyWidth
          }">
                </table-body>
                <div
                        v-if="$slots.append"
                        class="el-table__append-gutter"
                        :style="{ height: layout.appendHeight + 'px'}"></div>
            </div>
            <div
                    v-if="showSummary"
                    v-show="data && data.length > 0"
                    class="el-table__fixed-footer-wrapper"
                    ref="fixedFooterWrapper">
                <table-footer
                        fixed="left"
                        :border="border"
                        :sum-text="sumText || t('el.table.sumText')"
                        :summary-method="summaryMethod"
                        :store="store"
                        :style="{
            width: bodyWidth
          }"></table-footer>
            </div>
        </div>
        <div
                v-if="rightFixedColumns.length > 0"
                v-mousewheel="handleFixedMousewheel"
                class="el-table__fixed-right"
                ref="rightFixedWrapper"
                :style="[{
        width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '',
        right: layout.scrollY ? (border ? layout.gutterWidth : (layout.gutterWidth || 0)) + 'px' : ''
      },
      fixedHeight]">
            <div v-if="showHeader"
                 class="el-table__fixed-header-wrapper"
                 ref="rightFixedHeaderWrapper">
                <table-header
                        ref="rightFixedTableHeader"
                        fixed="right"
                        :border="border"
                        :store="store"
                        :style="{
            width: bodyWidth
          }"></table-header>
            </div>
            <div
                    class="el-table__fixed-body-wrapper"
                    ref="rightFixedBodyWrapper"
                    :style="[{
          top: layout.headerHeight + 'px'
        },
        fixedBodyHeight]">
                <table-body
                        fixed="right"
                        :store="store"
                        :stripe="stripe"
                        :row-class-name="rowClassName"
                        :row-style="rowStyle"
                        :highlight="highlightCurrentRow"
                        :style="{
            width: bodyWidth
          }">
                </table-body>
                <div
                        v-if="$slots.append"
                        class="el-table__append-gutter"
                        :style="{ height: layout.appendHeight + 'px' }"></div>
            </div>
            <div
                    v-if="showSummary"
                    v-show="data && data.length > 0"
                    class="el-table__fixed-footer-wrapper"
                    ref="rightFixedFooterWrapper">
                <table-footer
                        fixed="right"
                        :border="border"
                        :sum-text="sumText || t('el.table.sumText')"
                        :summary-method="summaryMethod"
                        :store="store"
                        :style="{
            width: bodyWidth
          }"></table-footer>
            </div>
        </div>
        <div
                v-if="rightFixedColumns.length > 0"
                class="el-table__fixed-right-patch"
                ref="rightFixedPatch"
                :style="{
        width: layout.scrollY ? layout.gutterWidth + 'px' : '0',
        height: layout.headerHeight + 'px'
      }"></div>
        <div class="el-table__column-resize-proxy" ref="resizeProxy" v-show="resizeProxyVisible"></div>
    </div>
</template>

<script type="text/babel">
    import ElCheckbox from 'element-ui/packages/checkbox';
    import {debounce, throttle} from 'throttle-debounce';
    import {addResizeListener, removeResizeListener} from 'element-ui/src/utils/resize-event';
    import Mousewheel from 'element-ui/src/directives/mousewheel';
    import Locale from 'element-ui/src/mixins/locale';
    import Migrating from 'element-ui/src/mixins/migrating';
    import {createStore, mapStates} from './store/helper';
    import TableLayout from './table-layout';
    import TableBody from './table-body';
    import TableHeader from './table-header';
    import TableFooter from './table-footer';
    import {parseHeight} from './util';

    let tableIdSeed = 1;

    export default {
        name: 'ElTable',

        mixins: [Locale, Migrating],

        directives: {
            Mousewheel
        },

        props: {
            data: {
                type: Array,
                default: function () {
                    return [];
                }
            },

            size: String,

            width: [String, Number],

            height: [String, Number],

            maxHeight: [String, Number],

            fit: {
                type: Boolean,
                default: true
            },

            stripe: Boolean,

            border: Boolean,

            rowKey: [String, Function],

            context: {},

            showHeader: {
                type: Boolean,
                default: true
            },

            showSummary: Boolean,

            sumText: String,

            summaryMethod: Function,

            rowClassName: [String, Function],

            rowStyle: [Object, Function],

            cellClassName: [String, Function],

            cellStyle: [Object, Function],

            headerRowClassName: [String, Function],

            headerRowStyle: [Object, Function],

            headerCellClassName: [String, Function],

            headerCellStyle: [Object, Function],

            highlightCurrentRow: Boolean,

            currentRowKey: [String, Number],

            emptyText: String,

            expandRowKeys: Array,

            defaultExpandAll: Boolean,

            defaultSort: Object,

            tooltipEffect: String,

            spanMethod: Function,

            selectOnIndeterminate: {
                type: Boolean,
                default: true
            },

            indent: {
                type: Number,
                default: 16
            },

            treeProps: {
                type: Object,
                default() {
                    return {
                        hasChildren: 'hasChildren',
                        children: 'children'
                    };
                }
            },

            lazy: Boolean,

            load: Function
        },

        components: {
            TableHeader,
            TableFooter,
            TableBody,
            ElCheckbox
        },

        methods: {
            getMigratingConfig() {
                return {
                    events: {
                        expand: 'expand is renamed to expand-change'
                    }
                };
            },

            setCurrentRow(row) {
                this.store.commit('setCurrentRow', row);
            },

            toggleRowSelection(row, selected) {
                this.store.toggleRowSelection(row, selected, false);
                this.store.updateAllSelected();
            },

            toggleRowExpansion(row, expanded) {
                this.store.toggleRowExpansionAdapter(row, expanded);
            },

            clearSelection() {
                this.store.clearSelection();
            },

            clearFilter(columnKeys) {
                this.store.clearFilter(columnKeys);
            },

            clearSort() {
                this.store.clearSort();
            },

            handleMouseLeave() {
                this.store.commit('setHoverRow', null);
                if (this.hoverState) this.hoverState = null;
            },

            updateScrollY() {
                const changed = this.layout.updateScrollY();
                if (changed) {
                    this.layout.notifyObservers('scrollable');
                    this.layout.updateColumnsWidth();
                }
            },

            handleFixedMousewheel(event, data) {
                const bodyWrapper = this.bodyWrapper;
                if (Math.abs(data.spinY) > 0) {
                    const currentScrollTop = bodyWrapper.scrollTop;
                    if (data.pixelY < 0 && currentScrollTop !== 0) {
                        event.preventDefault();
                    }
                    if (data.pixelY > 0 && bodyWrapper.scrollHeight - bodyWrapper.clientHeight > currentScrollTop) {
                        event.preventDefault();
                    }
                    bodyWrapper.scrollTop += Math.ceil(data.pixelY / 5);
                } else {
                    bodyWrapper.scrollLeft += Math.ceil(data.pixelX / 5);
                }
            },

            handleHeaderFooterMousewheel(event, data) {
                const {pixelX, pixelY} = data;
                if (Math.abs(pixelX) >= Math.abs(pixelY)) {
                    this.bodyWrapper.scrollLeft += data.pixelX / 5;
                }
            },

            // TODO 使用 CSS transform
            syncPostion: throttle(20, function () {
                const {scrollLeft, scrollTop, offsetWidth, scrollWidth} = this.bodyWrapper;
                const {headerWrapper, footerWrapper, fixedBodyWrapper, rightFixedBodyWrapper} = this.$refs;
                if (headerWrapper) headerWrapper.scrollLeft = scrollLeft;
                if (footerWrapper) footerWrapper.scrollLeft = scrollLeft;
                if (fixedBodyWrapper) fixedBodyWrapper.scrollTop = scrollTop;
                if (rightFixedBodyWrapper) rightFixedBodyWrapper.scrollTop = scrollTop;
                const maxScrollLeftPosition = scrollWidth - offsetWidth - 1;
                if (scrollLeft >= maxScrollLeftPosition) {
                    this.scrollPosition = 'right';
                } else if (scrollLeft === 0) {
                    this.scrollPosition = 'left';
                } else {
                    this.scrollPosition = 'middle';
                }
            }),

            bindEvents() {
                this.bodyWrapper.addEventListener('scroll', this.syncPostion, {passive: true});
                if (this.fit) {
                    addResizeListener(this.$el, this.resizeListener);
                }
            },

            unbindEvents() {
                this.bodyWrapper.removeEventListener('scroll', this.syncPostion, {passive: true});
                if (this.fit) {
                    removeResizeListener(this.$el, this.resizeListener);
                }
            },

            resizeListener() {
                if (!this.$ready) return;
                let shouldUpdateLayout = false;
                const el = this.$el;
                const {width: oldWidth, height: oldHeight} = this.resizeState;

                const width = el.offsetWidth;
                if (oldWidth !== width) {
                    shouldUpdateLayout = true;
                }

                const height = el.offsetHeight;
                if ((this.height || this.shouldUpdateHeight) && oldHeight !== height) {
                    shouldUpdateLayout = true;
                }

                if (shouldUpdateLayout) {
                    this.resizeState.width = width;
                    this.resizeState.height = height;
                    this.doLayout();
                }
            },

            doLayout() {
                if (this.shouldUpdateHeight) {
                    this.layout.updateElsHeight();
                }
                this.layout.updateColumnsWidth();
            },

            sort(prop, order) {
                this.store.commit('sort', {prop, order});
            },

            toggleAllSelection() {
                this.store.commit('toggleAllSelection');
            }

        },

        computed: {
            tableSize() {
                return this.size || (this.$ELEMENT || {}).size;
            },

            bodyWrapper() {
                return this.$refs.bodyWrapper;
            },

            shouldUpdateHeight() {
                return this.height ||
                    this.maxHeight ||
                    this.fixedColumns.length > 0 ||
                    this.rightFixedColumns.length > 0;
            },

            bodyWidth() {
                const {bodyWidth, scrollY, gutterWidth} = this.layout;
                return bodyWidth ? bodyWidth - (scrollY ? gutterWidth : 0) + 'px' : '';
            },

            bodyHeight() {
                const {headerHeight = 0, bodyHeight, footerHeight = 0} = this.layout;
                if (this.height) {
                    return {
                        height: bodyHeight ? bodyHeight + 'px' : ''
                    };
                } else if (this.maxHeight) {
                    const maxHeight = parseHeight(this.maxHeight);
                    if (typeof maxHeight === 'number') {
                        return {
                            'max-height': (maxHeight - footerHeight - (this.showHeader ? headerHeight : 0)) + 'px'
                        };
                    }
                }
                return {};
            },

            fixedBodyHeight() {
                if (this.height) {
                    return {
                        height: this.layout.fixedBodyHeight ? this.layout.fixedBodyHeight + 'px' : ''
                    };
                } else if (this.maxHeight) {
                    let maxHeight = parseHeight(this.maxHeight);
                    if (typeof maxHeight === 'number') {
                        maxHeight = this.layout.scrollX ? maxHeight - this.layout.gutterWidth : maxHeight;
                        if (this.showHeader) {
                            maxHeight -= this.layout.headerHeight;
                        }
                        maxHeight -= this.layout.footerHeight;
                        return {
                            'max-height': maxHeight + 'px'
                        };
                    }
                }
                return {};
            },

            fixedHeight() {
                if (this.maxHeight) {
                    if (this.showSummary) {
                        return {
                            bottom: 0
                        };
                    }
                    return {
                        bottom: (this.layout.scrollX && this.data.length) ? this.layout.gutterWidth + 'px' : ''
                    };
                } else {
                    if (this.showSummary) {
                        return {
                            height: this.layout.tableHeight ? this.layout.tableHeight + 'px' : ''
                        };
                    }
                    return {
                        height: this.layout.viewportHeight ? this.layout.viewportHeight + 'px' : ''
                    };
                }
            },

            emptyBlockStyle() {
                if (this.data && this.data.length) return null;
                let height = '100%';
                if (this.layout.appendHeight) {
                    height = `calc(100% - ${this.layout.appendHeight}px)`;
                }
                return {
                    width: this.bodyWidth,
                    height
                };
            },

            ...mapStates({
                selection: 'selection',
                columns: 'columns',
                tableData: 'data',
                fixedColumns: 'fixedColumns',
                rightFixedColumns: 'rightFixedColumns'
            })
        },

        watch: {
            height: {
                immediate: true,
                handler(value) {
                    this.layout.setHeight(value);
                }
            },

            maxHeight: {
                immediate: true,
                handler(value) {
                    this.layout.setMaxHeight(value);
                }
            },

            currentRowKey: {
                immediate: true,
                handler(value) {
                    if (!this.rowKey) return;
                    this.store.setCurrentRowKey(value);
                }
            },

            data: {
                immediate: true,
                handler(value) {
                    this.store.commit('setData', value);
                }
            },

            expandRowKeys: {
                immediate: true,
                handler(newVal) {
                    if (newVal) {
                        this.store.setExpandRowKeysAdapter(newVal);
                    }
                }
            }
        },

        created() {
            this.tableId = 'el-table_' + tableIdSeed++;
            this.debouncedUpdateLayout = debounce(50, () => this.doLayout());
        },

        mounted() {
            this.bindEvents();
            this.store.updateColumns();
            this.doLayout();

            this.resizeState = {
                width: this.$el.offsetWidth,
                height: this.$el.offsetHeight
            };

            // init filters
            this.store.states.columns.forEach(column => {
                if (column.filteredValue && column.filteredValue.length) {
                    this.store.commit('filterChange', {
                        column,
                        values: column.filteredValue,
                        silent: true
                    });
                }
            });

            this.$ready = true;
        },

        destroyed() {
            this.unbindEvents();
        },

        data() {
            const {hasChildren = 'hasChildren', children = 'children'} = this.treeProps;
            this.store = createStore(this, {
                rowKey: this.rowKey,
                defaultExpandAll: this.defaultExpandAll,
                selectOnIndeterminate: this.selectOnIndeterminate,
                // TreeTable 的相关配置
                indent: this.indent,
                lazy: this.lazy,
                lazyColumnIdentifier: hasChildren,
                childrenColumnName: children
            });
            const layout = new TableLayout({
                store: this.store,
                table: this,
                fit: this.fit,
                showHeader: this.showHeader
            });
            return {
                layout,
                isHidden: false,
                renderExpanded: null,
                resizeProxyVisible: false,
                resizeState: {
                    width: null,
                    height: null
                },
                // 是否拥有多级表头
                isGroup: false,
                scrollPosition: 'left'
            };
        }
    };
</script>
